import { Map } from 'ol'
import { unByKey } from 'ol/Observable'
import { EventsKey } from 'ol/events'
import { Pixel } from 'ol/pixel'
import { fromLonLat } from 'ol/proj'
import * as echarts from 'echarts'

/**
 * 飞线图配置选项
 */
export type FlyLineOptions = {
  /** 父级DOM元素 */
  parentDom: HTMLElement
  /** 是否可见 */
  visible?: boolean
}

/**
 * 飞线图类，用于在OpenLayers地图上展示ECharts飞线效果
 */
export class FlyLine {
  /** OpenLayers地图实例 */
  map: Map
  /** ECharts实例 */
  echarts: any
  /** 父级DOM元素 */
  parentDom: HTMLElement
  /** 是否可见 */
  visible: boolean
  /** ECharts图表配置 */
  chartOption: any
  /** 容器DOM元素 */
  box: HTMLElement | null
  /** ECharts图表实例 */
  chart: any
  /** 地图事件监听器1 */
  extentWatch1: EventsKey | EventsKey[] = []
  /** 地图事件监听器2 */
  extentWatch2: EventsKey | EventsKey[] = []

  /**
   * 构造函数
   * @param map OpenLayers地图实例
   * @param option 飞线图配置选项
   */
  constructor(map: Map, option: FlyLineOptions) {
    this.removeLayer()
    // 如果在服务器上使用该代码 可以将echart对象传入到option中
    this.echarts = echarts
    if (option.visible === undefined) {
      this.visible = true
    } else {
      this.visible = option.visible
    }
    this.map = map
    this.parentDom = option.parentDom
    this.box = null
    this.echarts.registerCoordinateSystem('openlayers', this.getE3CoordinateSystem(map))
    this.init(map)
  }

  /**
   * 初始化飞线图
   * @param map OpenLayers地图实例
   */
  init(map: Map) {
    this.setBaseMap(map)
    this.createLayer()
  }

  /**
   * 设置基础地图
   * @param map OpenLayers地图实例
   */
  setBaseMap(map: Map) {
    this.map = map
  }

  /**
   * 设置图表配置
   * @param option ECharts配置选项
   */
  setChartOption(option: any) {
    this.chartOption = option
    this.setCharts()
  }

  /**
   * 设置可见性
   * @param bool 是否可见
   */
  setVisible(bool: boolean) {
    if (!this.box || this.visible === bool) return
    this.box.hidden = !bool
    this.visible = bool
    bool === true && this.setCharts()
  }

  /**
   * 开始刷新
   */
  refreshBegin() {
    if (this.box) {
      this.box.hidden = true
    }
  }

  /**
   * 刷新中
   */
  refreshing() {
    this.setCharts()
  }

  /**
   * 刷新结束
   */
  refreshEnd() {
    if (this.box) {
      this.box.hidden = false
    }
  }

  /**
   * 绑定事件
   * @param eventName 事件名称
   * @param handler 事件处理函数
   * @param context 上下文
   */
  on(eventName: string, handler: () => void, context: any) {
    this.chart.on(eventName, handler, context)
  }

  /**
   * 解绑事件
   * @param eventName 事件名称
   * @param handler 事件处理函数
   * @param context 上下文
   */
  off(eventName: string, handler: () => void, context: any) {
    this.chart.off(eventName, handler, context)
  }

  /**
   * 设置图表
   */
  setCharts() {
    if (!this.visible) return
    if (this.chartOption == null || this.chartOption === 'undefined') return
    var baseExtent = this.map.getView().getViewStateAndExtent().extent
    // 判断是否使用了mark类型标签，每次重绘要重新转换地理坐标到屏幕坐标
    // 根据地图extent,重绘echarts
    if (baseExtent) {
      this.chartOption.xAxis = { show: false, min: baseExtent[0], max: baseExtent[2] }
      this.chartOption.yAxis = { show: false, min: baseExtent[1], max: baseExtent[3] }
      this.chart.setOption(this.chartOption)
      this.chartOption.animation = false
    }
    if (this.box) {
      const size = this.map.getSize()
      if (size) {
        this.box.style.width = size[0] + 'px'
        this.box.style.height = size[1] + 'px'
      }
    }
  }

  /**
   * 创建图层容器
   */
  createLayer() {
    var box = (this.box = document.createElement('div'))
    box.setAttribute('id', 'echartsData')
    box.setAttribute('name', 'echartsData')
    const size = this.map.getSize()
    if (size) {
      box.style.width = size[0] + 'px'
      box.style.height = size[1] + 'px'
    }

    box.style.position = 'absolute'
    box.style.top = '0'
    box.style.left = '0'
    this.box = box
    if (this.parentDom) {
      this.parentDom.appendChild(box)
    } else {
      var parent_1 = document.getElementsByClassName('ol-viewport')[0]
      parent_1.appendChild(box)
    }
    this.chart = this.echarts.init(box)
    this.startMapEventListeners()
  }

  /**
   * 移除图层
   */
  removeLayer() {
    this.visible = false
    this.extentWatch1 && unByKey(this.extentWatch1)
    this.extentWatch2 && unByKey(this.extentWatch2)
    if (this.box) {
      this.box.outerHTML = ''
    }
    this.box = null
    this.chart && this.chart.dispose()
    this.chart = null
    this.chartOption = null
  }

  /**
   * 开始地图事件监听
   */
  startMapEventListeners() {
    var map = this.map
    this.extentWatch1 = map.on('movestart', () => {
      if (this.box) this.box.hidden = true
    })
    this.extentWatch2 = map.on('moveend', () => {
      if (!this.visible) return
      this.setCharts()
      this.chart.resize()
      if (this.box) this.box.hidden = false
    })
  }

  /**
   * 获取ECharts坐标系统
   * @param map OpenLayers地图实例
   * @returns ECharts坐标系统
   */
  getE3CoordinateSystem(map: Map) {
    var CoordSystem = function CoordSystem(map: Map) {
      this.map = map
      this._mapOffset = [0, 0]
    }
    CoordSystem.prototype.create = function (ecModel: any) {
      ecModel.eachSeries(function (seriesModel: any) {
        if (seriesModel.get('coordinateSystem') === 'openlayers') {
          seriesModel.coordinateSystem = new (CoordSystem as any)(map)
        }
      })
    }
    CoordSystem.prototype.getDimensionsInfo = function () {
      return ['x', 'y']
    }
    CoordSystem.prototype.dimensions = ['x', 'y']
    CoordSystem.prototype.setMapOffset = function setMapOffset(mapOffset: Array<number>) {
      this._mapOffset = mapOffset
    }
    CoordSystem.prototype.dataToPoint = function dataToPoint(data: Array<number>) {
      var projectionCode = this.map.getView().getProjection().getCode()
      var point = [data[0], data[1]]
      if (projectionCode === 'EPSG:3857') {
        point = fromLonLat(point) // 转为3857
      }
      var px = map.getPixelFromCoordinate(point)
      var mapOffset = this._mapOffset
      return [px[0] - mapOffset[0], px[1] - mapOffset[1]]
    }
    CoordSystem.prototype.pointToData = function pointToData(pt: Array<number>) {
      var mapOffset = this._mapOffset
      var screentPoint: Pixel = [pt[0] + mapOffset[0], pt[1] + mapOffset[1]]
      var data = map.getCoordinateFromPixel(screentPoint)
      return [data[0], data[1]]
    }
    CoordSystem.prototype.getViewRect = function getViewRect() {
      // return new graphic.BoundingRect(0, 0, this.map.getSize()[0], this.map.getSize()[1])
    }
    CoordSystem.prototype.getRoamTransform = function getRoamTransform() {
      // return matrix.create()
    }
    return CoordSystem
  }
}
